<?php

    class PhpDbDriverOciStatement extends PhpDbAbstractStatement // Statement du Driver ORACLE (pdo_oci est experimental)
    {
        const DEBUG = 0;

        public $pending = array();
        public $params = array();
        public $ocistatement = '';
        public $ociparams = array();

        # constructeur
        public function __construct ( $host, $statement )
        {
            parent::__construct($host,$statement);

            $ocistatement = explode('?',$statement);
            for($i=1;$i<count($ocistatement); $i++)
                $ocistatement[$i] = $i.' '.$ocistatement[$i];
            $this->ocistatement = implode(' :bypos',$ocistatement);
            $this->_restart();

            // TODO setAttribute
        }

        # methodes PhpDb
        public function debug($msg)
        {
            if(!self::DEBUG)
                return;
            if($msg == 'parse')
                $msg .= ' '.$this->ocistatement;
            if($msg == 'execute')
                $msg .= ' '.print_r($this->ociparams,true);
            echo $msg.'<br />';
        }
        public function exception($type,$message,$noadd=false)
        {
            try{
                parent::exception($type,$message,true);
            } catch(Exception $e) {
                $params = array();
                foreach($this->ociparams as $name=>$data)
                    $params[$name] = print_r($data['value'],true).' ('.$data['mode'].'-'.$this->_getTypeTexte($data['type']).')';
                $message = $e->getMessage()."\n\n".$this->ocistatement."\n\n".print_r($params,true);
                throw new Exception($message);
            }
        }
        public function _getParam($parameter)
        {
            $p = $parameter;
            if(is_numeric($parameter))
            {
                $p = ':bypos'.$parameter;
                if(strpos($this->ocistatement,$p) === false)
                {
                    $ps = explode(':',$this->ocistatement);
                    $p = ':'.substr($ps[$parameter],0,max(strpos($ps[$parameter],' '),strpos($ps[$parameter],')')));
                    if($p == ':')
                        $p = ':'.$ps[$parameter];
                }
            }
            return $p;
        }
        public function _getType($pdo_type,$value)
        {
            $oci_type = NULL;
            switch($pdo_type)
            {
                case PDO::PARAM_BOOL: $oci_type=SQLT_INT; break; // TODO test
                case PDO::PARAM_NULL: $oci_type=SQLT_INT; break; // TODO test
                case PDO::PARAM_INT:  $oci_type=is_null($value)?SQLT_CHR:SQLT_INT; break;
                case PDO::PARAM_STR:  $oci_type=SQLT_CHR; break;
                case PDO::PARAM_LOB:  $oci_type=OCI_B_BLOB; break;
                default: throw new Exception('Type inconnu : "'.$pdo_type.'"'); break;
            }
            return $oci_type;
        }
        public function _getTypeTexte($oci_type)
        {
            switch($oci_type)
            {
                case SQLT_INT: return 'INT';
                case SQLT_CHR: return 'CHR';
                case OCI_B_BLOB: case SQLT_BLOB: return 'BLOB';
            }
            return '??';
        }
        public function _restart()
        {
            $this->debug('parse');
            if(!empty($this->cursor))
                $this->closeCursor();
            $this->cursor = oci_parse($this->host->conn,$this->ocistatement);
            if(!$this->cursor)
            {
                $e = oci_error($this->conn);
                throw new Exception(htmlentities($e['message']));
            }
            foreach($this->ociparams as $parameter=>$data)
            {
                $this->values[$parameter] = $data['value'];
                oci_bind_by_name($this->cursor,$parameter,$this->values[$parameter],-1,$data['type']);
            }
        }

        # methodes PDO
        /*bool*/ public function bindColumn ( $column , &$param , $type = NULL , $maxlen = NULL , $driverdata = NULL )
        {
            throw new Exception('NOT IMPLEMENTED : '.__FILE__.'@'.__LINE__.';'.__FUNCTION__);
        }
        /*bool*/ public function bindParam ( $parameter , &$variable, $data_type = PhpDb::PARAM_STR , $length = NULL , $driver_options = NULL )
        {
            $this->debug('bindRef');
            $this->params[$parameter] = array('value'=>$variable,'mode'=>'ref','type'=>$data_type);
            $parameter = $this->_getParam($parameter);
            $data_type = $this->_getType($data_type,$variable);
            if($data_type == OCI_B_BLOB || $data_type == SQLT_BLOB)
            {
                if(is_resource($variable))
                {
                    $data = '';
                    rewind($variable);
                    while(!feof($variable))
                        $data .= fread($variable,128);
                }
                else
                    $data = $variable;
                $res = OCINewDescriptor($this->host->conn, OCI_D_LOB);
                $value = $res;
                $this->pending[] = array($res,$data);
            }
            if(is_array($variable))
            {
                $str = array();
                $k = 1;
                foreach($variable as $v)
                {
                    $name = $parameter.'_'.($k++);
                    $str[] = $name;
                    $this->ociparams[$name] = array('value'=>$v,'mode'=>'ref','type'=>$data_type);
                }
                $this->ocistatement = str_replace('= '.$parameter,'IN ( '.implode(' , ',$str).' )',$this->ocistatement);
                $this->_restart();
                return true;
            }
            $this->ociparams[$parameter] = array('value'=>$variable,'mode'=>'ref','type'=>$data_type);
            return oci_bind_by_name($this->cursor,$parameter,$variable,-1,$data_type);
        }
        /*bool*/ public function bindValue ( $parameter , $value , $data_type = PhpDb::PARAM_STR )
        {
            $this->debug('bindVal');
            $this->params[$parameter] = array('value'=>$value,'mode'=>'val','type'=>$data_type);
            $parameter = $this->_getParam($parameter);
            $data_type = $this->_getType($data_type,$value);
            if($data_type == OCI_B_BLOB || $data_type == SQLT_BLOB)
            {
                if(is_resource($value))
                {
                    $data = '';
                    rewind($value);
                    while(!feof($value))
                        $data .= fread($value,128);
                }
                else
                    $data = $value;
                $res = OCINewDescriptor($this->host->conn, OCI_D_LOB);
                $res->writeTemporary($data,OCI_TEMP_BLOB);
                $value = $res;
                $this->pending[] = array($res,$data);
            }
            if(is_array($value))
            {
                $str = array();
                $k = 1;
                foreach($value as $k=>$v)
                {
                    $name = $parameter.'_'.($k++);
                    $str[] =  $name;
                    $this->ociparams[$name] = array('value'=>$v,'mode'=>'val','type'=>$data_type);
                }
                $this->ocistatement = str_replace('= '.$parameter,'IN ( '.implode(' , ',$str).' )');
                $this->_restart();
                return true;
            }
            $this->values[$parameter] = $value;
            $this->ociparams[$parameter] = array('value'=>$value,'mode'=>'val','type'=>$data_type);
            return oci_bind_by_name($this->cursor,$parameter,$this->values[$parameter],-1,$data_type);
        }
        /*bool*/ public function closeCursor ( )
        {
            $this->debug('close');
            return oci_free_statement($this->cursor);
        }
        /*int*/ public function columnCount ( )
        {
            return oci_num_fields($this->cursor);
        }
        /*bool*/ public function debugDumpParams ( )
        {
            throw new Exception('NOT IMPLEMENTED : '.__FILE__.'@'.__LINE__.';'.__FUNCTION__);
        }
        /*string*/ public function errorCode ( )
        {
            throw new Exception('NOT IMPLEMENTED : '.__FILE__.'@'.__LINE__.';'.__FUNCTION__);
        }
        /*array*/ public function errorInfo ( )
        {
            throw new Exception('NOT IMPLEMENTED : '.__FILE__.'@'.__LINE__.';'.__FUNCTION__);
        }
        /*bool*/ public function execute ( $input_parameters = array() )
        {
            $this->debug('execute');
            ob_start();
            $result = oci_execute($this->cursor, $this->host->transaction||!empty($this->pending)?OCI_NO_AUTO_COMMIT:OCI_COMMIT_ON_SUCCESS);
            foreach($this->pending as $x)
            {
                list($res,$data) = $x;
                $res->save($data);
                @$res->close();
                $res->free();
            }
            if(!empty($this->pending))
                $this->host->commit();
            $x = ob_get_clean();
            if(!$result || $x)
            {
                $e = oci_error($this->cursor);
                $this->exception(self::EXCEPTION_QUERY,htmlentities($e['message']).' '.$x);
            }
            return $result;
        }
        /*mixed*/ public function fetch ( $fetch_style = PhpDb::FETCH_BOTH , $cursor_orientation = PhpDb::FETCH_ORI_NEXT , $cursor_offset = 0 )
        {
            $this->debug('fetch');
            switch($fetch_style)
            {
                case PhpDb::FETCH_BOTH: $fetch = oci_fetch_array($this->cursor, OCI_BOTH|OCI_RETURN_NULLS|OCI_RETURN_LOBS); break;
                case PhpDb::FETCH_ASSOC: $fetch = oci_fetch_array($this->cursor, OCI_ASSOC|OCI_RETURN_NULLS|OCI_RETURN_LOBS); break;
                case PhpDb::FETCH_NUM: $fetch = oci_fetch_array($this->cursor, OCI_NUM|OCI_RETURN_NULLS|OCI_RETURN_LOBS); break;
                case PhpDb::FETCH_OBJ: throw new Exception('Fetch OBJ pas gere');
                case PhpDb::FETCH_COLUMN: throw new Exception('Fetch COLUMN pas gere');
                default: throw new Exception('Fetch mode inconnu : "'.$fetch_style.'"');
            }
            return $fetch;
        }
        /*array*/ public function fetchAll ( $fetch_style = PhpDb::FETCH_BOTH , $column_index = 0 , $ctor_args = array() )
        {
            $this->debug('fetchAll');
            $flags = OCI_FETCHSTATEMENT_BY_ROW;
            $flags = OCI_RETURN_NULLS;
            switch($fetch_style)
            {
                case PhpDb::FETCH_BOTH: $flags = $flags|OCI_NUM|OCI_ASSOC; $fct = 'oci_fetch_array'; break;
                case PhpDb::FETCH_ASSOC: $flags = $flags|OCI_ASSOC; $fct = 'oci_fetch_assoc'; break;
                case PhpDb::FETCH_NUM: $flags = $flags|OCI_NUM; $fct = 'oci_fetch_row'; break;
                case PhpDb::FETCH_OBJ: throw new Exception('Fetch OBJ pas gere');
                case PhpDb::FETCH_COLUMN: throw new Exception('Fetch COLUMN pas gere'); $flags = OCI_FETCHSTATEMENT_BY_COLUMN; break;
                default: throw new Exception('Fetch mode inconnu : "'.$fetch_style.'"');
            }
            $output = array();
            while($row = oci_fetch_array($this->cursor,$flags))
            {
                foreach($row as $k=>$v)
                    if(is_object($v))
                        $row[$k] = new PhpDbOciLobPatch($v);
                $output[] = $row;
            }
            if($fetch_style == PhpDb::FETCH_COLUMN)
                return array_shift($output);
            return $output;
        }
        /*string*/ public function fetchColumn ( $column_number = 0 )
        {
            $row = $this->fetch();
            return $row[$column_number];
        }
        /*mixed*/ public function fetchObject ( $class_name = "stdClass", $ctor_args = NULL )
        {
            throw new Exception('NOT IMPLEMENTED : '.__FILE__.'@'.__LINE__.';'.__FUNCTION__);
        }
        /*mixed*/ public function getAttribute ( $attribute )
        {
            if(isset($this->attributes[$attribute]))
                return $this->attributes[$attribute];
            return NULL;
        }
        /*array*/ public function getColumnMeta ( $column )
        {
            $column++;
            $meta = array();
            $meta['null'] = oci_field_is_null($this->cursor,$column);
            $meta['name'] = oci_field_name($this->cursor,$column);
            $meta['precision'] = oci_field_precision($this->cursor,$column);
            $meta['scale'] = oci_field_scale($this->cursor,$column);
            $meta['length'/*'size'*/] = oci_field_size($this->cursor,$column);
            $meta['type_raw'] = oci_field_type_raw($this->cursor,$column);
            $meta['type'] = oci_field_type($this->cursor,$column);
            $meta['data_type'] = $meta['type'];
            switch($meta['type_raw'])
            {
                case SQLT_CHR: $meta['pdo_type'] = PDO::PARAM_STR; $meta['native_type'] = 'string'; break;
                case SQLT_INT: $meta['pdo_type'] = PDO::PARAM_INT; $meta['native_type'] = 'int'; break;
                case SQLT_BLOB: $meta['pdo_type'] = PDO::PARAM_LOB; $meta['native_type'] = 'resource'; break;
            }
            return $meta;
        }
        /*bool*/ public function nextRowset ( )
        {
            throw new Exception('NOT IMPLEMENTED : '.__FILE__.'@'.__LINE__.';'.__FUNCTION__);
        }
        /*int*/ public function rowCount ( )
        {
            return oci_num_rows($this->cursor);
        }
        /*bool*/ public function setAttribute ( $attribute , $value )
        {
            $this->attributes[$attribute] = $value; // TODO en faire quelque chose
        }
        /*bool*/ public function setFetchMode ( $mode , $params=NULL )
        {
            throw new Exception('NOT IMPLEMENTED : '.__FILE__.'@'.__LINE__.';'.__FUNCTION__);
        }
    }
    class PhpDbOciLobPatch
    {
        private $resource = NULL;
        public function __construct($resource)
        {
            $this->resource = $resource;
        }
        public function __toString()
        {
            if(is_object($this->resource))
            {
                fxBench::custom('oci-lob','load');
                $this->resource = $this->resource->load();
                $this->resource = fxDbTable::Resource($this->resource);
                fxBench::custom('oci-lob','load',true);
            }
            return $this->resource;
        }
    }

?>
